/*****************************************************************************/
/**
 *  @file   SPHFile.cpp
 *  @author Naohisa Sakamoto
 */
/*----------------------------------------------------------------------------
 *
 *  Copyright (c) Visualization Laboratory, Kyoto University.
 *  All rights reserved.
 *  See http://www.viz.media.kyoto-u.ac.jp/kvs/copyright/ for details.
 *
 *  $Id$
 */
/*****************************************************************************/
#include "SPHFile.h"
#include <kvs/Assert>
#include <kvs/Message>
#include <kvs/Endian>
#include <string>
#include <cstdio>


namespace
{

/*===========================================================================*/
/**
 *  @brief  Skips N bytes.
 *  @param  fp [in] file pointer
 */
/*===========================================================================*/
template <int N>
inline void SkipBytes( FILE* fp )
{
    char frecord[N];
    fread( frecord, N, 1, fp );
}

/*===========================================================================*/
/**
 *  @brief  Returns a value.
 *  @param  fp [in] file pointer
 *  @param  swap [in] flag for byte swap
 *  @return value
 */
/*===========================================================================*/
template <typename T>
inline T GetValue( FILE* fp, bool swap )
{
    T value;
    fread( &value, sizeof(T), 1, fp );
    if ( swap ) kvs::Endian::Swap( &value );
    return value;
}

/*===========================================================================*/
/**
 *  @brief  Returns a value.
 *  @param  fp [in] file pointer
 *  @param  dtype [in] data type (1:float/int, 2:double/long int)
 *  @param  swap [in] flag for byte swap
 *  @return value
 */
/*===========================================================================*/
template <typename T32, typename T64>
inline T64 GetValue( FILE* fp, int dtype, bool swap )
{
    KVS_ASSERT( dtype == 1 || dtype == 2 );
    T64 value = 0;
    if ( dtype == 1 ) value = GetValue<T32>( fp, swap );
    else if ( dtype == 2 ) value = GetValue<T64>( fp, swap );
    return value;
}

/*===========================================================================*/
/**
 *  @brief  Returns a value-array.
 *  @param  fp [in] file pointer
 *  @param  size [in] size of array (number of elements)
 *  @param  swap [in] flag for byte swap
 *  @return value-array
 */
/*===========================================================================*/
template <typename T>
inline kvs::ValueArray<T> GetValues( FILE* fp, size_t size, bool swap )
{
    kvs::ValueArray<T> values( size );
    fread( values.data(), sizeof(T), size, fp );
    if ( swap ) kvs::Endian::Swap( values.data(), values.size() );
    return values;
}

/*===========================================================================*/
/**
 *  @brief  Returns an any-value-array.
 *  @param  fp [in] file pointer
 *  @param  size [in] size of array (number of elements)
 *  @param  dtype [in] data type (1:float/int, 2:double/long int)
 *  @param  swap [in] flag for byte swap
 *  @return any-value-array
 */
/*===========================================================================*/
template <typename T32, typename T64>
inline kvs::AnyValueArray GetValues( FILE* fp, size_t size, int dtype, bool swap )
{
    KVS_ASSERT( dtype == 1 || dtype == 2 );
    kvs::AnyValueArray values;
    if ( dtype == 1 ) values = GetValues<T32>( fp, size, swap );
    else if ( dtype == 2 ) values = GetValues<T64>( fp, size, swap );
    return values;
}

}

namespace kvsbloodvis
{

namespace util
{

/*===========================================================================*/
/**
 *  @brief  Constructs a new SPHFile class.
 *  @param  filename [in] input filename
 *  @param  header_only [in] if true, the heeader information will be only read
 */
/*===========================================================================*/
SPHFile::SPHFile( const std::string& filename, bool header_only )
{
    if ( header_only ) { this->read_header( filename ); }
    else { this->read( filename ); }
}

/*===========================================================================*/
/**
 *  @brief  Returns data array as AnyValueArray.
 *  @return data array
 */
/*===========================================================================*/
kvs::AnyValueArray SPHFile::data() const
{
    if ( m_data.size() == 0 )
    {
        SPHFile temp( filename(), false );
        return temp.data();
    }
    return m_data;
}

/*===========================================================================*/
/**
 *  @brief  Prints the file information.
 *  @param  os [in] output stream
 *  @param  indent [in] indent
 */
/*===========================================================================*/
void SPHFile::print( std::ostream& os, const kvs::Indent& indent ) const
{
    os << indent << "Filename : " << filename() << std::endl;
    os << indent << "svType : " << m_sv_type << std::endl;
    os << indent << "dType : " << m_d_type << std::endl;
    os << indent << "IMAX : " << m_max[0] << std::endl;
    os << indent << "JMAX : " << m_max[1] << std::endl;
    os << indent << "KMAX : " << m_max[2] << std::endl;
    os << indent << "XORG : " << m_org[0] << std::endl;
    os << indent << "YORG : " << m_org[1] << std::endl;
    os << indent << "ZORG : " << m_org[2] << std::endl;
    os << indent << "XPITCH : " << m_pitch[0] << std::endl;
    os << indent << "YPITCH : " << m_pitch[1] << std::endl;
    os << indent << "ZPITCH : " << m_pitch[2] << std::endl;
    os << indent << "STEP : " << m_step << std::endl;
    os << indent << "TIME : " << m_time << std::endl;
}

/*===========================================================================*/
/**
 *  @brief  Reads the file.
 *  @param  filename [in] filename
 *  @return true if the reading processing is done successfully
 */
/*===========================================================================*/
bool SPHFile::read( const std::string& filename )
{
    setFilename( filename );
    setSuccess( true );

    FILE* fp = fopen( filename.c_str(), "r" );
    if ( !fp )
    {
        kvsMessageError( "Cannot open %s.", filename.c_str() );
        setSuccess( false );
        return false;
    }

    read_header( fp );
    read_data( fp );
    fclose( fp );

    return true;
}

/*===========================================================================*/
/**
 *  @brief  Reads the header information.
 *  @param  filename [in] filename
 *  @return true if the reading process is done successfully
 */
/*===========================================================================*/
bool SPHFile::read_header( const std::string& filename )
{
    setFilename( filename );
    setSuccess( true );

    FILE* fp = fopen( filename.c_str(), "r" );
    if ( !fp )
    {
        kvsMessageError( "Cannot open %s.", filename.c_str() );
        setSuccess( false );
        return false;
    }

    read_header( fp );
    fclose( fp );

    return true;
}

/*===========================================================================*/
/**
 *  @brief  Reads the data.
 *  @param  filename [in] filename
 *  @return true if the reading process is done successfully
 */
/*===========================================================================*/
bool SPHFile::read_data( const std::string& filename )
{
    setFilename( filename );
    setSuccess( true );

    FILE* fp = fopen( filename.c_str(), "r" );
    if ( !fp )
    {
        kvsMessageError( "Cannot open %s.", filename.c_str() );
        setSuccess( false );
        return false;
    }

    read_data( fp );
    fclose( fp );

    return true;
}

/*===========================================================================*/
/**
 *  @brief  Reads the header information.
 *  @param  fp [in] file pointer
 */
/*===========================================================================*/
void SPHFile::read_header( FILE* fp )
{
    const bool swap = true;

    ::SkipBytes<4>( fp );
    m_sv_type = ::GetValue<kvs::Int32>( fp, swap );
    m_d_type = ::GetValue<kvs::Int32>( fp, swap );
    ::SkipBytes<4>( fp );

    ::SkipBytes<4>( fp );
    m_max[0] = ::GetValue<kvs::Int32,kvs::Int64>( fp, m_d_type, swap );
    m_max[1] = ::GetValue<kvs::Int32,kvs::Int64>( fp, m_d_type, swap );
    m_max[2] = ::GetValue<kvs::Int32,kvs::Int64>( fp, m_d_type, swap );
    ::SkipBytes<4>( fp );

    ::SkipBytes<4>( fp );
    m_org[0] = ::GetValue<kvs::Real32,kvs::Real64>( fp, m_d_type, swap );
    m_org[1] = ::GetValue<kvs::Real32,kvs::Real64>( fp, m_d_type, swap );
    m_org[2] = ::GetValue<kvs::Real32,kvs::Real64>( fp, m_d_type, swap );
    ::SkipBytes<4>( fp );

    ::SkipBytes<4>( fp );
    m_pitch[0] = ::GetValue<kvs::Real32,kvs::Real64>( fp, m_d_type, swap );
    m_pitch[1] = ::GetValue<kvs::Real32,kvs::Real64>( fp, m_d_type, swap );
    m_pitch[2] = ::GetValue<kvs::Real32,kvs::Real64>( fp, m_d_type, swap );
    ::SkipBytes<4>( fp );

    ::SkipBytes<4>( fp );
    m_step = ::GetValue<kvs::Int32,kvs::Int64>( fp, m_d_type, swap );
    m_time = ::GetValue<kvs::Real32,kvs::Real64>( fp, m_d_type, swap );
    ::SkipBytes<4>( fp );

    m_end_of_header = ftell( fp );
}

/*===========================================================================*/
/**
 *  @brief  Reads the data.
 *  @param  fp [in] file pointer
 */
/*===========================================================================*/
void SPHFile::read_data( FILE* fp )
{
    if ( m_end_of_header == 0 ) read_header( fp );
    fseek( fp, m_end_of_header, SEEK_SET );

    const bool swap = true;

    ::SkipBytes<4>( fp );
    size_t nnodes = m_max[0] * m_max[1] * m_max[2];
    size_t ncomponents = m_sv_type == 1 ? 1 : 3;
    m_data = ::GetValues<kvs::Real32,kvs::Real64>( fp, nnodes * ncomponents, m_d_type, swap );
    ::SkipBytes<4>( fp );
}

} // end of namespace util

} // end of namespace kvsbloodvis
